Java中的消息摘要、加密与数字签名

您所在的位置:网站首页 create a cipher message Java中的消息摘要、加密与数字签名

Java中的消息摘要、加密与数字签名

2022-05-08 03:21| 来源: 网络整理| 查看: 265

Java中的消息摘要与加密 消息摘要(Message Digest)

消息在互联网传输过程中不可避免的会经历多个中间人,它可能是代理服务器,也可能是路由、网关等等。 消息在传输过程中有可能被人窃听,甚至是修改。为了保证消息在传输过程中的完整性我们可以使用消息摘要。

消息摘要算法会为每一个不同的消息生成其独有的摘要,这就像人的指纹一样。有两种生成消息摘要的办法, 第一种是对消息进行异或运算,所得到的消息摘要被称为checksum;第二种是利用哈希函数生成消息摘要。第一种方法 的安全性较弱,可以通过修改消息得到想要的checksum;利用哈希函数的方式,产生的消息摘要要安全的多,如果修改了消息的1个bit,那么理论上摘要中50%的内容都会发生改变。

Java支持的摘要生成算法包括:

MD2和MD5,生成的摘要长度为128位SHA-1, 生成的摘要长度为160位SHA-256, SHA-383和SHA-512, 分别生成256、383和512位的消息摘要

我们下面就看看如何利用MD5算法生成消息的摘要。

import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Security; public class MessageDigestExample { static { if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { System.out.println("Did not find BouncyCastleProvider"); Security.addProvider(new BouncyCastleProvider()); } } public static void main(String[] args) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchProviderException { if(args.length != 1) { System.err.println("Usage: java MessageDigestExample text"); System.exit(1); } byte[] plainText = args[0].getBytes("UTF8"); MessageDigest messageDigest = MessageDigest.getInstance("MD5", BouncyCastleProvider.PROVIDER_NAME); System.out.println("\n" + messageDigest.getProvider().getInfo()); messageDigest.update(plainText); System.out.println( "\nDigest: " ); System.out.println( new String( messageDigest.digest(), "UTF8") ); } }

MessageDigest 类用于生成消息摘要,它接受第三方的算法实现。

初始化MessageDigest时,可接受第三方库所实现的摘要算法;update()方法接受待生成摘要的消息作为参数;digest()方法用于生成最终的摘要。 信息认证码(Message authentication code)

如果是利用密钥来生成消息摘要的话,所生成的消息摘要被称为信息认证码。

import javax.crypto.KeyGenerator; import javax.crypto.Mac; import javax.crypto.SecretKey; import java.io.UnsupportedEncodingException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; public class MessageAuthenticationCodeExample { public static void main(String[] args) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException { if(args.length != 1) { System.err.println("Usage: java MessageAuthenticationCodeExample text"); System.exit(1); } byte[] plainText = args[0].getBytes("UTF8"); System.out.println( "\nStart generating key" ); // 生成密钥 KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5"); SecretKey md5key = keyGen.generateKey(); System.out.println( "Finish generating key: \n" + md5key.toString() + "\n" ); // 创建Mac实例用于生成信息认证码 Mac mac = Mac.getInstance("HmacMD5"); mac.init(md5key); mac.update(plainText); System.out.println( "\n" + mac.getProvider().getInfo() ); System.out.println( "\nMAC: " ); // doFinal()方法生成信息认证码 System.out.println( new String( mac.doFinal(), "UTF8") ); } } 消息加密

消息摘要只能帮助确认消息的完整性,只有消息加密在能确保消息在传输过程中不被人窃听。消息加密可以分为两种方式, 如果消息发送双方利用同一个密钥对消息进行加密解密,这种方式称为对称加密;如果使用不同的密钥对分别对消息进行加密和解密操作,这种方式被称为非对称加密。

对称加密

对称加密中,消息发送方与接收方都保存着一份相同的密钥。发送方在发生消息之前,利用密钥对消息进行加密,生成加密后的密文消息,然后将密文消息发送给接收方; 接收方接收到密文后,再用密钥对密文进行解密,还原出原来的消息。

在加密过程中可以针对单个bit进行逐一的加密,也可以将多个bit凑成bit块,对bit块加密。前者称为流加密(stream ciphers),后者成为块加密(block ciphers)。 在块加密过程中如果被加密的字符串的长度不够一个加密块的长度,就需要对字符串进行填充。

import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import java.io.UnsupportedEncodingException; import java.security.InvalidKeyException; import java.security.Key; import java.security.NoSuchAlgorithmException; public class PrivateExample { public static void main(String[] args) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { if (args.length != 1) { System.err.println("Usage: java PrivateExample text"); System.exit(1); } byte[] plainText = args[0].getBytes("UTF8"); //Step 1: create private key System.out.println( "\nStart generating DES key" ); KeyGenerator keyGen = KeyGenerator.getInstance("DES"); keyGen.init(56); Key key = keyGen.generateKey(); System.out.println( "Finish generating DES key" ); //Step 2: create cipher Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); System.out.println( "\n" + cipher.getProvider().getInfo() ); cipher.init(Cipher.ENCRYPT_MODE, key); //Step 3: use cipher to encrypt message byte[] cipherText = cipher.doFinal(plainText); System.out.println( "Finish encryption: " ); System.out.println( new String(cipherText, "UTF8") ); //Step 4: use cipher to decrypt message cipher.init(Cipher.DECRYPT_MODE, key); byte[] decryptText = cipher.doFinal(cipherText); System.out.println( "Finish decryption: " ); System.out.println( new String(decryptText, "UTF8") ); } }

Java JDK支持的对称密钥生成算法包括:

DESTripleDESAESRC2, RC4和RC5BlowfishPBE

Java JDK支持的加密算法包括:

ECBCBCCFBOFBPCBC 非对称加密

密钥同步是对称加密中一个难以解决的问题,因为在发送加密消息之前,消息发送双方需要同步密钥,而密钥可能被别人截获。 而非对称加密可以解决这个问题,非对称加密过程中会使用到两个密钥,一个专门用于加密消息,被称为公钥,一个专门用来解密消息,被称为私钥。 公钥是可以公开的,而私钥必须留在自己的手上。在发送加密消息前,消息发送双方先将公钥发送给对方,消息发送者利用对方的公钥对消息进行加密, 然后将加密后的密文发送给接收方,接收方收到密文后,再用自己的私钥解开密文。

加密流程

import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import java.io.UnsupportedEncodingException; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; public class PublicExample { public static void main(String[] args) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, NoSuchProviderException { if (args.length != 1) { System.err.println("Usage: java PublicExample text"); System.exit(1); } byte[] plainText = args[0].getBytes("UTF8"); //Step 1: generate a RSA key System.out.println( "\nStart generating RSA key" ); KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); keyGen.initialize(1024); KeyPair keyPair = keyGen.generateKeyPair(); System.out.println( "Finish generating RSA key" ); //Step 2: get an RSA cipher object and print the provider Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); System.out.println( "\n" + cipher.getProvider().getInfo() ); //Step 3: encrypt the plaintext using the public key cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic()); byte[] encryptedText = cipher.doFinal(plainText); System.out.println( "Finish encryption: " ); System.out.println( new String(encryptedText, "UTF8") ); //Step 4: decrypt the encryptedText using the private key System.out.println( "\nStart decryption" ); cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate()); byte[] decryptedText = cipher.doFinal(encryptedText); System.out.println( "Finish decryption: " ); System.out.println( new String(decryptedText, "UTF8") ); } }

由于非对称加密方法的加密速度慢,在实际使用过程中,通常使用非对称加密的方式来发送对称加密的密钥(如HTTPS协议)。

数字签名

在非对称加密过程中,存在一个漏洞:消息接收者无法确定消息是否真正来自发送者。举个例子,Bob和Alice利用非对称加密方式传输消息, Eve位于Bob和Alice之间,Eve可以截取到Bob发送给Alice的公钥,然后将自己伪装成Alice,利用公钥向Bob发送消息,然而Bob无法确认这个消息到底是来自Alice还是Eve, 这被称为中间人攻击(Man-in-the-Middle attack)。

Man-in-the-Middle attack

消息数字签名(digital signature)是防止中间人攻击的有效手段,数字签名的原理是,在发送消息前,Alice利用自己的私钥对消息摘要进行加密, 加密后的消息摘要称为数字签名,然后连同加密后的消息发送给Bob。Bob收到消息后,先用自己的私钥解密消息,然后利用Alice的公钥解密数字签名作为老的消息摘要, Bob对收到的消息生成新的消息摘要,与老的摘要进行对比,如果一致则可确定消息来自Alice。在消息传输过程中,由于Eve无法获取到Bob和Alice的私钥所以无法伪造数字签名。

import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import java.io.UnsupportedEncodingException; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** * * 使用数字签名的目的在于防止中间人攻击(Man-in-the-Middle attack),例如Bob和Alice利用非对称密钥发送信息,由于公钥是公开的, * Bob无法知道自己收到的消息是否真的来自于Alice,因为Eve可能在截取到Alice的公钥,然后用Alice的公钥向Bob发送信息。 * * 利用数字签名方法,可以有效的防止中间人攻击,其原理如下: * 1. 消息发送者生成消息的digest。 * 2. 发送者使用私钥对digest进行加密。 * 3. 发送者将加密后的digest和消息发送给接收者。 * 4. 接收者使用发送者的公钥解密digest,然后重新计算消息的digest。 * 5. 对比两个digest是否一致,如果一致表示消息未被修改。 * * 由于私钥只保存于发送者手中,所以接收者能确保消息未被修改。 */ public class DigitalSignatureExample { public static void main(String[] args) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { if (args.length != 1) { System.err.println("Usage: java DigitalSignature1Example text"); System.exit(1); } byte[] plainText = args[0].getBytes("UTF8"); //Step 1: generate message digest MessageDigest messageDigest = MessageDigest.getInstance("MD5"); System.out.println( "\n" + messageDigest.getProvider().getInfo() ); messageDigest.update(plainText); byte[] md = messageDigest.digest(); System.out.println( "\nDigest: " ); System.out.println( new String( md, "UTF8") ); //Step 2: generate RSA keypair System.out.println("\nStart generating RSA key"); KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); keyGen.initialize(1024); KeyPair keyPair = keyGen.generateKeyPair(); System.out.println("Finish generating RSA key"); //Step 3: Get RSA cipher and list the provider Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); System.out.printf("\n" + cipher.getProvider().getInfo()); //Step 4: Encrypt the message digest with the RSA private key to create the signature System.out.printf("\nStart encryption"); cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate()); byte[] cipherText = cipher.doFinal(md); System.out.printf("\nFinish encryption: "); System.out.printf(new String(cipherText, "UTF8")); //Step 5: Verify Signature, start by decrypting the signature with the RSA public key System.out.printf("\nStart decryption"); cipher.init(Cipher.DECRYPT_MODE, keyPair.getPublic()); byte[] newMD = cipher.doFinal(cipherText); System.out.printf("\nFinish decryption"); // here may throw "java.util.UnknownFormatConversionException", it is a bug of jdk... // if that happened, change a input string and try again. System.out.printf(new String(newMD, "UTF8")); //Then, recreate the message digest from the plaintext to simulate what a recipient must do System.out.printf("\nStart signature verification"); messageDigest.reset(); messageDigest.update(plainText); byte[] oldMD = messageDigest.digest(); //Verify that the two message digests match int len = newMD.length; if (len > oldMD.length) { System.out.printf("Signature failed, length error"); System.exit(1); } for (int i=0; i < len; i++) { if (oldMD[i] != newMD[i]) { System.out.printf("Signature failed, element error"); System.exit(1); } } System.out.printf("Signature verified"); } }

上面的方法比较繁琐,Java提供了Signature类,能更加方便的生成数字签名,代码如下:

import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Signature; import java.security.spec.X509EncodedKeySpec; /** * * 利用Signature类,能够更加方便的实现数字签名,签名原理与example1相同 */ public class DigitalSignatureExample2 { public static void main(String[] args) { if (args.length != 1) { System.out.printf("Usage: DigitalSignatureExample2 nameOfFileToSign"); } else try { //Step 1: generate DSA keypair System.out.printf("\nStart generating DSA key"); KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA", "SUN"); SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN"); keyGen.initialize(1024, random); KeyPair keyPair = keyGen.genKeyPair(); System.out.printf("\nFinish generating DSA key"); //Step 2: create signature object and init it with private key System.out.printf("\nStart creating signature object"); Signature signature = Signature.getInstance("SHA1withDSA", "SUN"); signature.initSign(keyPair.getPrivate()); System.out.printf("\nFinish creating signature object"); //Step 3: Supply the Signature Object the Data to be signed System.out.printf("\nStart signing the file"); FileInputStream fis = new FileInputStream(args[0]); BufferedInputStream bufin = new BufferedInputStream(fis); byte[] buffer = new byte[1024]; int len; while ((len = bufin.read(buffer)) >= 0) { signature.update(buffer, 0, len); } bufin.close(); fis.close(); byte[] realSig = signature.sign(); System.out.printf("\n" + new String(realSig, "UTF8")); System.out.printf("\nFinish signing the file"); //Step 4: Save signature and public key in files System.out.printf("\nSaving signature and key pair to file"); FileOutputStream sigfos = new FileOutputStream("sig"); sigfos.write(realSig); sigfos.close(); System.out.printf("\nSaved signature to sig"); byte[] pubKey = keyPair.getPublic().getEncoded(); FileOutputStream pubKeyfos = new FileOutputStream("pubk"); pubKeyfos.write(pubKey); pubKeyfos.close(); System.out.printf("\nSaved public key to pubk"); byte[] privateKey = keyPair.getPrivate().getEncoded(); FileOutputStream privateKeyfos = new FileOutputStream("prik"); privateKeyfos.write(privateKey); privateKeyfos.close(); System.out.printf("\nSaved private key to prik"); //Step 6: Verify Signature verifySignature("pubk", "sig", "data"); } catch (Exception e) { System.err.println("Caught exception " + e.toString()); } } public static void verifySignature(String publicKeyFile, String signatureFile, String dataFile) { try { //Step 1: Load public key and signature from file FileInputStream keyfis = new FileInputStream(publicKeyFile); byte[] encKey = new byte[keyfis.available()]; keyfis.read(encKey); keyfis.close(); X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(encKey); KeyFactory keyFactory = KeyFactory.getInstance("DSA", "SUN"); PublicKey publicKey = keyFactory.generatePublic(pubKeySpec); FileInputStream sigfis = new FileInputStream(signatureFile); byte[] sigToVerify = new byte[sigfis.available()]; sigfis.read(sigToVerify); sigfis.close(); //Step 2: Initialize the Signature Object for Verification Signature sig = Signature.getInstance("SHA1withDSA", "SUN"); sig.initVerify(publicKey); //Step 3: Supply the Signature Object with the data to be verified FileInputStream datafis = new FileInputStream(dataFile); BufferedInputStream bufin = new BufferedInputStream(datafis); byte[] buffer = new byte[1024]; int len; while (bufin.available() != 0) { len = bufin.read(buffer); sig.update(buffer, 0, len); } bufin.close(); datafis.close(); //Step 4: Verify the Signature boolean verifies = sig.verify(sigToVerify); System.out.printf("Signature verifies: " + verifies); } catch (Exception e) { e.printStackTrace(); } } }


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3